package Phase1;

import java.io.BufferedReader;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;

import static org.junit.Assert.*;
public class Client extends Thread
{
	/**
	 * 
	 * Class for sending command for class operation to a Server thread. Destination could be in other machine.
	 * 
	 * @author djb0646
	 * @version 0.9
	 */
	private static final int TIMEOUT = 5000;
	private static final int RETRY = 15;
	/**  
	 * empty constructor
	 */
	public Client()
	{
	}

	
	/**
	 * sends request to target location with return value.
	 * @param location  location is an Ip. It could be things like : "localhost", "John's pc" ,127.0.0.1 .....
	 * @param message the detailed request to server.
	 *   
	 */
	public static Object sendData(String location, int _port, Object message) 
		throws IOException, ClassNotFoundException
	{	
		assertNotNull(location);
		Port port = new Port(_port);
		Object result = new Object();
		boolean notSent = true;
		int retries = 0;
		while (notSent && retries < RETRY)
		{
			try
			{
				Socket target = (Socket) setLinkToServer(InetAddress.getByName(location).getHostName(), port.getPort());
				//target.setSoTimeout(TIMEOUT);
			
				ObjectOutputStream output = new ObjectOutputStream(target.getOutputStream()); 
				ObjectInputStream input = new ObjectInputStream(target.getInputStream());
				
				output.writeObject(message);
				output.flush(); 
				result = input.readObject();
				notSent = false;
				output.close();
				input.close();
				target.close();
				if(result instanceof Exception)
				{
					throw new NullPointerException("Command Failed. Recieved " 
							+ ((Exception) result).getClass().getName()
							+ "\n\t" + ((Exception)result).getMessage());
				}
			}
			catch (SocketTimeoutException e)
			{
				System.out.println("Connection timed out. Trying again.");
			}
			catch (SocketException e)
			{
				System.out.println("SocketException. Trying again.");
			}
			retries++;
		}
		if(retries == RETRY)
		{
			result = new SocketException("The Client failed to send and recieve an object in the specified number of tries.");
		}
		return result;
	}
	/**
	 * 
	 * @param globalid the destination
	 * @param message the detailed request to server site.
	 * @return  The result of running the request in server site.
	 * @throws IOException If connection trouble occurs.
	 * @throws ClassNotFoundException  If send /get object in trouble
	 */
	public static Object sendData(GlobalID globalid, Object message) 
		throws IOException, ClassNotFoundException
	{	
		return sendData(globalid.getMachineIP(), globalid.getPort(), message);
	}
	/**
	 * 
	 * @param location the destination of server site.
	 * @param port the common port for server site.
	 * @param message the detailed request to server site.
	 * @throws IOException If connection trouble occurs.
	 * @throws ClassNotFoundException  If send /get object in trouble
	 */
	public static void voidSendData(String location, int _port, Object message) 
		throws IOException, ClassNotFoundException, NullPointerException
	{	
		assertNotNull(location);
		Port port = new Port(_port);
		boolean notSent = true;
		int retries = 0;
		while (notSent && retries < RETRY)
		{
			try
			{
				Socket target = setLinkToServer(InetAddress.getByName(location).getHostName(), port.getPort());
				//target.setSoTimeout(TIMEOUT);
			 	
				ObjectOutputStream output = new ObjectOutputStream(target.getOutputStream()); 
				ObjectInputStream input = new ObjectInputStream(target.getInputStream());
				
			 	
				output.writeObject(message);
				Object result = input.readObject();
				notSent = false;
				output.flush();   
				output.close();
				input.close();
				target.close();
				if(result instanceof Exception)
				{
					throw new NullPointerException("Command Failed. Recieved "
							+ ((Exception) result).getClass().getName()
							+ "\n\t" + ((Exception)result).getMessage());
				}
			}
			catch (SocketTimeoutException e)
			{
				//catch might skiped in coverage test
				System.out.println("Connection timed out. Trying again.");
			}
			catch (SocketException e)
			{
				//catch might skiped in coverage test
				System.out.println("SocketException. Trying again.");
			}
			retries++;
		}
		if(retries == RETRY)
		{
			throw new SocketException("The Client failed to send and recieve an object in the specified number of tries.");
		}
	 	
	}
	/**
	 * 
	 * @param globalid the destination
	 * @param message the detailed request to server site.
	 * @throws IOException If connection trouble occurs.
	 * @throws ClassNotFoundException  If send /get object in trouble
	 */
	public static void voidSendData(GlobalID globalid, Object message) throws IOException, ClassNotFoundException
	{	
		voidSendData(globalid.getMachineIP(), globalid.getPort(), message);
	}
	/**
	 * 
	 * @param location location of server site.
	 * @param socket  hearing port of server site.
	 * @return Server's special socket for this client only.
	 * @throws IOException connection fail
	 * @throws ClassNotFoundException  passing object fail
	 */
	public static Socket setLinkToServer(String location, int _port) throws IOException, ClassNotFoundException
	{
		assertNotNull(location);
		Port port = new Port(_port);
		
		String address = InetAddress.getByName(location).getHostAddress();
		Socket target = new Socket(address, port.getPort());
		//target.setSoTimeout(TIMEOUT);
		
		ObjectOutputStream output = new ObjectOutputStream(target.getOutputStream()); 
		ObjectInputStream input = new ObjectInputStream(target.getInputStream());
	  
		 
		int dataPort = input.readInt(); 
		Socket temp =  new Socket(address, dataPort); 
		output.close();
		input.close();
		target.close();
		
		return temp;
	}
	/**
	 * 
	 * @param globalid define the location of server site.
	 * @return Server's special socket for this client only.
	 * @throws IOException connection fail
	 * @throws ClassNotFoundException  passing object fail
	 */
	public static Object setLinkToServer(GlobalID globalid, int _port) throws IOException, ClassNotFoundException
	{	
		Port port = new Port(_port);	
		return setLinkToServer(globalid.getMachineIP(), port.getPort());
	}
	
	/**
	 * testing driver
	 * @param args
	 * command passin. no use.
	 */
	public static void main(String[] args)
	{
		
		BufferedReader input =  new BufferedReader(new InputStreamReader(System.in));
		 
		try 
		{
			System.out.println("Enter your distiny:");
			
			String location = input.readLine();
			
			System.out.println("Enter your message:");
			
			String message = input.readLine();
			
			System.out.println("Enter your port:");
			
			int port = input.read();
			Client.sendData(location, port, message);
			
		} 
		catch (IOException e)
		{
			//catch might skiped in coverage test
			e.printStackTrace();
		}
		catch (ClassNotFoundException e)
		{
			//catch might skiped in coverage test
			e.printStackTrace();
		}
	}
/*	/**
	 * 
	 * @author djb0646
	 * this is a class that tend to halt another thread after a time period
	 *
	 */
/*	private class listener extends Thread
	{
		
		private Socket socket;
		public listener(Socket socket)
		{
			this.socket = socket;			
			this.start();
		}
		public void run() 
		{
			try
			{ 
			 	Thread.sleep(SLEEP_5000);
				
				/*
				 * The socket must complete all interactions and interrupt this thread
				 * or this this thread will close the socket being used. 
				 */
/*		 		if(socket != null && !socket.isClosed())
				{					 
					socket.close();
				}
			} catch (InterruptedException e) 
			{ 
				;
			}   
			catch (IOException e) 
			{
				e.printStackTrace();
			} 
		}
	}
*/
}
